﻿//	The simplest function, increment a counter.

#pragma once


#include "cuda_runtime.h"

#include "Windows.h"

#include <vector>


typedef unsigned char byte;



#define returnOnErrorMacro(cudaStatus) { if(cudaStatus != cudaSuccess) {return operation_errors_t(esgCudaError, cudaStatus, cudaGetErrorString(cudaStatus),__FUNCTION__, __LINE__, __FILE__);} }
#define getOperationErrorFromCudaStatusMacro(cudaStatus) (operation_errors_t(esgCudaError, cudaStatus, cudaGetErrorString(cudaStatus),__FUNCTION__, __LINE__, __FILE__));

#define ESG_ENDS_COUNT 2
#define MAX_DISJUNCT_CLUSTERS_HEADS 32
#define MAX_DISJUNCT_CLUSTERS_GROUPS_MAP_COUNT 32
#define MAX_DISJUNCT_CLUSTERS_COUNT 8



__constant__ byte esgEnds[ESG_ENDS_COUNT];
__constant__ unsigned char clusterStarts[MAX_DISJUNCT_CLUSTERS_HEADS];// = { 29, 32, 52, 62, 87, 94, 95, 138 }; // clusters interval (8 + 1 intervals)
__constant__ unsigned char clusterGroups[MAX_DISJUNCT_CLUSTERS_GROUPS_MAP_COUNT];// = { 1, 2, 3, 0, 1, 2, 0, 1, 3 }; // group for each interval 0 - num groups <= intervals-1



__device__ __forceinline__ float conditionalDiv(char filterSign, char filterDiff, float a, float b){
	return (a / b)*filterSign + filterDiff;
}

__device__ __forceinline__ float conditionalReturn(char filterSign, char filterDiff, float a){
	return a * filterSign + filterDiff;
}

__device__ __forceinline__ float normalizeRawScore(float score)
{
	return (score - 50) / 50;
}

__device__ __forceinline__ float denormalizeRawScore(float score)
{
	float denormalized=(score*50 + 50);
	float cappedScore;
	cappedScore= (denormalized < 0) ? 0 : denormalized;
	cappedScore = (cappedScore > 100) ? 100 : cappedScore;
	return cappedScore;
}


template<class T>
struct SharedMemory
{
	__device__ operator T *()
	{
		extern __shared__ int __smem[];
		return (T *)__smem;
	}

	__device__ operator const T *() const
	{
		extern __shared__ int __smem[];
		return (T *)__smem;
	}
};




//__global__ void TB2_reduction(float* s_data){
//	unsigned int thid = threadIdx.x;
//	unsigned int stride = 1;
//	unsigned int i, ai, bi;
//	for (unsigned int d = B; d>0; d >>= 1){
//		__syncthreads();
//		if (thid < d){
//			i = 2 * stride*thid;
//			ai = i;
//			bi = ai + stride;
//			ai += (ai >> 5); //log2(nBanks)=5
//			bi += (bi >> 5); //log2(nBanks)=5
//			//Reduction
//			s_data[ai] = s_data[ai] + s_data[bi];
//		}//if
//		stride <<= 1;
//	}//for
//	//The result is in s_data[0]
//}

namespace esgKernels
{
	__host__ __device__ __forceinline__ float ComputePercentile(int currentGroupCount, int computedRank)
	{
		return (currentGroupCount > 1) ? static_cast<float>(currentGroupCount - computedRank)*100 / (currentGroupCount - 1) : 100;
	}



	struct CompanyAttributes
	{
		int segment_index;
		float score;
	};
	

	__global__  inline void computeRatingsKernel(
		const float* v
		, const float* p
		, const unsigned char* flags
		, const unsigned char* attributes
		, unsigned char filter
		, float* ratings
		, int* companiesIndices
		, float* indicatorRatings
		, float* aggregatedWeights
		, int companiesNo
		, int indicatorsNo
		, byte disjunctClustersCount
		, byte clustersCount)
	{
		int line = (blockDim.x * blockIdx.x + threadIdx.x);
		if (line >= companiesNo)
			return;

		float clusterGroupsResults[MAX_DISJUNCT_CLUSTERS_COUNT] = { 0, 0, 0, 0, 0, 0, 0, 0 };
		float clusterGroupsWeights[MAX_DISJUNCT_CLUSTERS_COUNT] = { 0, 0, 0, 0, 0, 0, 0, 0 };


		float clusterGroupScore = 0;
		float clusterGroupWeight = 0;
		byte currentClusterGroup = 0;

		float score = 0;
		float weight = 0;

		float groupRelativeWeight = 0;
		float groupScore = 0;
		byte currentGroup = 0;

		//extern __shared__ float esg_score[];
		extern __shared__ float sharedArray[];
		float* esg_score = (float*)&sharedArray;
		float* esg_weight = (float*)&esg_score[3 * blockDim.x];

		int index = line;
		int offset;
		char filterSign = -1;
		char filterDiff = -4;
		if (flags[line] * (((attributes[line] & filter) == filter) || (!filter)))
		{
			filterSign = 1;
			filterDiff = 0;
		}

		for (int i = 0; i < indicatorsNo; i++)
		{
			if (i == esgEnds[currentGroup])
			{
				offset = threadIdx.x * 3 + currentGroup;
				*(esg_score + offset) = conditionalDiv(filterSign, filterDiff, groupScore, groupRelativeWeight);
				*(esg_weight + offset) = groupRelativeWeight;
				score += groupScore;
				weight += groupRelativeWeight;
				++currentGroup;
				groupRelativeWeight = 0;
				groupScore = 0;
			}

			if (i == clusterStarts[currentClusterGroup])
			{
				clusterGroupsResults[clusterGroups[currentClusterGroup]] = clusterGroupScore + clusterGroupsResults[clusterGroups[currentClusterGroup]];
				clusterGroupsWeights[clusterGroups[currentClusterGroup]] = clusterGroupWeight + clusterGroupsWeights[clusterGroups[currentClusterGroup]];

				++currentClusterGroup;
				clusterGroupScore = 0;
				clusterGroupWeight = 0;
			}

			float pCurrent = p[index];
			float indicatorRating = pCurrent * v[index];

			if (indicatorRatings)
				indicatorRatings[index] = indicatorRating;

			groupRelativeWeight += pCurrent;
			groupScore += indicatorRating;

			clusterGroupScore += indicatorRating;
			clusterGroupWeight += pCurrent;
			index = index + companiesNo;
		}

		clusterGroupsResults[clusterGroups[currentClusterGroup]] = clusterGroupScore + clusterGroupsResults[clusterGroups[currentClusterGroup]];
		clusterGroupsWeights[clusterGroups[currentClusterGroup]] = clusterGroupWeight + clusterGroupsWeights[clusterGroups[currentClusterGroup]];

		offset = threadIdx.x * 3 + currentGroup;
		*(esg_score + offset) = conditionalDiv(filterSign, filterDiff, groupScore, groupRelativeWeight);
		*(esg_weight + offset) = groupRelativeWeight;

		score += groupScore;
		weight += groupRelativeWeight;

		ratings[line] = conditionalReturn(filterSign, filterDiff, score);
		if (aggregatedWeights)
		{
			aggregatedWeights[line] = weight;
		}

		for (int i = 0; i < disjunctClustersCount; i++)
		{
			index = (4 + i) * companiesNo + line;
			ratings[index] = conditionalReturn(filterSign, filterDiff, clusterGroupsResults[i]);
			if (aggregatedWeights)
			{
				aggregatedWeights[index] = clusterGroupsWeights[i];
			}
		}

		for (int i = 0; i < clustersCount; i++)
		{
			companiesIndices[i * companiesNo + line] = line;
		}

#pragma unroll
		//TODO :this should be removed and dynamically passed
		for (int i = 0; i < 3; i++)
		{
			index = line + (i + 1) * companiesNo;
			ratings[index] = esg_score[threadIdx.x * 3 + i];
			if (aggregatedWeights)
			{
				aggregatedWeights[index] = esg_weight[threadIdx.x * 3 + i];
			}
		}
	}


	__global__ inline void computeGovernanceRatingsKernel(const float* weightsD, const float* scoresD, const float* modifiers, const float* keyIssuesWeight, float* ratings, float* key_issues_weightedScores, float* key_issues_weights, int companiesNo, int indicatorsNo, int issuesCount)
	{
		int line = (blockDim.x * blockIdx.x + threadIdx.x);
		if (line >= companiesNo)
			return;

		float clusterGroupsResults[MAX_DISJUNCT_CLUSTERS_COUNT] = { 0, 0, 0, 0, 0, 0, 0, 0 };

		float clusterGroupScore = 0;
		float clusterGroupWeight = 0;
		byte currentClusterGroup = 0;
		byte lastClusterStartIndex = 0;
		byte lastClusterEndIndex = clusterStarts[currentClusterGroup];



		float modifierValue = 0;


		int index = line;


		for (int i = 0; i < indicatorsNo; i++)
		{
			if (i == clusterStarts[currentClusterGroup])
			{
				clusterGroupsResults[clusterGroups[currentClusterGroup]] = modifierValue + (clusterGroupScore + clusterGroupsResults[clusterGroups[currentClusterGroup]]) / clusterGroupWeight;
				lastClusterStartIndex = i;
				++currentClusterGroup;
				lastClusterEndIndex = i > clusterGroups[currentClusterGroup] ? indicatorsNo : clusterGroups[currentClusterGroup];
				clusterGroupScore = 0;
				clusterGroupWeight = 0;
				modifierValue = 0;
			}

			float pCurrent = weightsD[index];
			float sCurrent = normalizeRawScore(scoresD[index]);//(scoresD[index] - 50) / 50;
			float indicatorRating = pCurrent * sCurrent;

			//𝑀𝑜𝑑𝑖𝑓𝑖𝑒𝑟 = ∑𝑖 = 1,𝑛∑𝑗 = 𝑖 + 1,𝑛−1    𝑤𝑖,𝑗 ×(𝑣𝑖 + 𝑣𝑗)× | 𝑣𝑖𝑣𝑗 |
			for (int j = lastClusterStartIndex; j < lastClusterEndIndex; j++)
			{
				float weight = modifiers[i * indicatorsNo + j];
				float adjacentScore = normalizeRawScore(scoresD[line + j * companiesNo]);
				modifierValue = modifierValue + (sCurrent + adjacentScore) * fabsf(sCurrent * adjacentScore) * weight;
			}


			clusterGroupScore += indicatorRating;
			clusterGroupWeight += pCurrent;
			index = index + companiesNo;
		}

		clusterGroupsResults[clusterGroups[currentClusterGroup]] = modifierValue + (clusterGroupScore + clusterGroupsResults[clusterGroups[currentClusterGroup]]) / clusterGroupWeight;


		float issueWeight = 0;
		float finalScore = 0;
		float issuescore;

#pragma unroll
		for (int i = 0; i < issuesCount; i++)
		{
			index = (1 + i) * companiesNo + line;
			ratings[index] = denormalizeRawScore(clusterGroupsResults[i]);

			issuescore = keyIssuesWeight[i * companiesNo + line];
			issueWeight += issuescore;
			if (key_issues_weightedScores)
			{
				key_issues_weightedScores[index] = ratings[index] * issuescore;
			}

			if (key_issues_weights)
			{
				key_issues_weights[index] = issuescore;
			}
			finalScore += ratings[index] * issuescore;
		}


		ratings[line] = finalScore / issueWeight;

		if (key_issues_weightedScores)
		{
			key_issues_weightedScores[line] = ratings[line];
		}

		if (key_issues_weights)
		{
			key_issues_weights[line] = issueWeight;
		}
	}


	__global__ inline void repeatCompanyFlagsKernel(int companiesNo, const int* companiesFlags, const float* ratings, int keyIssuesCount, int companyGroupTypeCount, const int* companyAttributesTypeCount, CompanyAttributes* repeatedCompanyGroupFlags, int* companiesMap)
	{
		int line = (blockDim.x * blockIdx.x + threadIdx.x);
		if (line >= companiesNo)
			return;
		int index;
		int index_comparative_group;
		for (int keyIssueIndex = 0; keyIssueIndex < keyIssuesCount;keyIssueIndex++)
		{
			index = keyIssueIndex * companiesNo + line;

			

			for (int flags_type_index = 0; flags_type_index < companyGroupTypeCount; flags_type_index++)
			{
				index_comparative_group = keyIssuesCount * companiesNo * flags_type_index + index;
				companiesMap[index_comparative_group] = line;
				int offset = keyIssuesCount * (flags_type_index > 0 ? companyAttributesTypeCount[flags_type_index - 1] : 0);
				int companyOffset = flags_type_index*companiesNo + line;
				repeatedCompanyGroupFlags[index_comparative_group].segment_index = offset + keyIssueIndex * companyAttributesTypeCount[flags_type_index] + companiesFlags[companyOffset];
				repeatedCompanyGroupFlags[index_comparative_group].score = ratings[index];


				//indexPeerGroup = keyIssuesCount*companiesNo + index;
				//repeatedCompanyGroupFlags[indexPeerGroup].segment_index = keyIssuesCount*regionGroupCount + keyIssueIndex*peerGroupCount + companiesFlags[line + companiesNo];
				//repeatedCompanyGroupFlags[indexPeerGroup].score = ratings[index];
				//companiesMap[indexPeerGroup] = line;
			}
			

		}

	}


	inline void ComputeRatings(int size, const float* v, const float* p, const unsigned char* flags, const unsigned char* attributes, unsigned char filter, float* ratings, int* companiesIndices, float* indicatorRatings, float* aggregatedWeights, int companiesNo, int indicatorsNo, byte disjunctClustersCount, byte clustersCount)
	{
		int threads = 128;
		int blocks = companiesNo / threads;
		if (companiesNo % threads != 0)
		{
			++blocks; // ensure that all companies are assigned to a thread.
		}

		int sharedSizeMemory;
		sharedSizeMemory = (2 * threads * (size)+1) * sizeof(float);

		computeRatingsKernel << < blocks, threads, sharedSizeMemory >> >(v, p, flags, attributes, filter, ratings, companiesIndices, indicatorRatings, aggregatedWeights, companiesNo, indicatorsNo, disjunctClustersCount, clustersCount);
	}

	inline void ComputeRatings(int size, const float* weightsD, const float* scoresD, const float* modifiers, const float* keyIssues, float* ratings, float* key_issues_scores, float* key_issues_weights, int companiesNo, int indicatorsNo, int keyIssuesCount)
	{
		int threads = 128;
		int blocks = companiesNo / threads;
		if (companiesNo % threads != 0)
		{
			++blocks; // ensure that all companies are assigned to a thread.
		}


		computeGovernanceRatingsKernel << < blocks, threads >> >(weightsD, scoresD, modifiers, keyIssues, ratings, key_issues_scores, key_issues_weights, companiesNo, indicatorsNo, keyIssuesCount);
	}

	inline void RepeatCompanyGroupFlags(int companiesNo, const int* companyFlags, const float* ratings, int keyIssuesCount, int companyGroupTypeCount,const int* companyAttributesTypeCount, CompanyAttributes* &repeatedCompanyAttributes, int* &companiesMap)
	{
		int threads = 512;
		int blocks = companiesNo / threads;
		if (companiesNo % threads != 0)
		{
			++blocks; // ensure that all companies are assigned to a thread.
		}

		int rankingCount = companiesNo*keyIssuesCount*companyGroupTypeCount;

		cudaError_t cudaStatus;
		cudaStatus= cudaMallocHost((void**)&repeatedCompanyAttributes, sizeof(esgKernels::CompanyAttributes) * rankingCount);
		cudaStatus = cudaMalloc((void**)&companiesMap, sizeof(int) * rankingCount);//throws CudaException

		repeatCompanyFlagsKernel << < blocks, threads >> >(companiesNo, companyFlags, ratings, keyIssuesCount, companyGroupTypeCount, companyAttributesTypeCount,repeatedCompanyAttributes, companiesMap);
	}

	inline void ComputeRanking(int ratingsCount, const CompanyAttributes* companyAttrybutes, int* ranking, float* percentile, int* groupCount)
	{
		int current_group_count = 0;
		int computed_rank = 0;
		int last_group_index = 0;
		for (int index = 0; index < ratingsCount; index++)
		{
			if (index > 0)
				if (companyAttrybutes[index].segment_index != companyAttrybutes[index - 1].segment_index || (index==ratingsCount-1)) //head group change
				{
					for (int i = last_group_index; i < (index == ratingsCount - 1 ? index + 1 : index); i++)
					{
						groupCount[i] = current_group_count + (index == ratingsCount - 1);
						percentile[i] = ComputePercentile(groupCount[i], ranking[i]);
					}
					last_group_index = index;
					computed_rank = 0;
					current_group_count = 0;
				}
			++current_group_count;

			if ((index == last_group_index) || (index>0 && companyAttrybutes[index].score != companyAttrybutes[index - 1].score))
			{
				computed_rank=current_group_count;
			}


			ranking[index] = computed_rank;

			
		}
	}


};